﻿using Fish.Utility;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace MassiveBattle.Manager
{
	/// <summary>
	/// 전투 기본 클래스.
	/// </summary>
	public abstract partial class BaseManager : MonoBehaviour
	{
		public static BaseManager Instance { get; private set; }
		public UI UI { get; private set; }
		public Report Report { get; private set; }
		public TakePool TakePool { get; private set; }
		public GameObjectPool GameObjectPool { get; private set; }

		#region Take Data
		private IDictionary<string, EventlineTake> takeDic = new Dictionary<string, EventlineTake>();
		private IEventlinePlayer takeCur;
		private int takeIndexCur = 0;
		[SerializeField]
		private StringArray[] takePlayOrder;

		[SerializeField]
		private GameObject[] cameraList;
		[SerializeField]
		private float cameraItpTime = 1;
		#endregion

		#region Effect Data
		[SerializeField]
		private GameObject effectTacticsPosition;
		[SerializeField]
		private GameObject[] effectTacticsList;

		public Material EffectHitMaterial;
		public float EffectHitTime = 0.1f;

		public GameObject[] EffectStateList = new GameObject[1];
		#endregion

		#region Left Time Data
		public float LeftTime { get; private set; }

		private float leftTimeCur;

		public float LeftTimeCur
		{
			get
			{
				return leftTimeCur;
			}

			private set
			{
				leftTimeCur = Mathf.Clamp(value, 0f, LeftTime);

				UI.LeftTimeCur = LeftTimeCur;
			}
		}
		#endregion

		#region Tactics Data
		private float tacticsPointCur;
		private ETactics tacticsType;

		public float TacticsPointPerCount { get; private set; }
		public int TacticsCount { get; private set; }
		private float TacticsPointCur
		{
			get
			{
				return tacticsPointCur;
			}

			set
			{
				tacticsPointCur = Mathf.Clamp(value, 0, TacticsPointPerCount * TacticsCount);

				int count = TacticsCountCur;

				UI.TacticsRatioCur = count == TacticsCount ? 1 : TacticsPointCur / TacticsPointPerCount - count;
				UI.TacticsCountCur = count;
				UI.IsCanUseTactics = IsCanUseTactics;
			}
		}
		private int TacticsCountCur
		{
			get
			{
				return Mathf.FloorToInt(TacticsPointCur / TacticsPointPerCount);
			}

			set
			{
				TacticsPointCur = value * TacticsPointPerCount;
			}
		}
		public ETactics TacticsType
		{
			get
			{
				return tacticsType;
			}

			private set
			{
				tacticsType = value;

				UI.TacticsActive = TacticsType;
				UI.IsCanUseTactics = IsCanUseTactics;
			}
		}
		public bool IsCanUseTactics
		{
			get
			{
				return TacticsCountCur >= 1 && TacticsType == ETactics.NONE;
			}
		}
		#endregion

		#region Formation Data
		[SerializeField]
		protected Transform formationPositionRoot;
		public Transform phasePositionRoot;

		public abstract bool IsRaid { get; }
		#endregion

		#region Current Data
		protected int typeFlag;

		public int Phase { get; protected set; }

		public SideData[] DataList { get; private set; }

		public IProtectMonitor Monitor { get; set; }

		public bool IsActive { get; protected set; }
		#endregion

		#region Init
		private void Awake()
		{
			Application.targetFrameRate = -1;

			Instance = this;
			UI = FindObjectOfType<UI>();
			UI.SetManager(this);
			Report = Report.Create();
			TakePool = TakePool.Create();
			GameObjectPool = GameObjectPool.Create();

			DataList = new SideData[ESide.END];
			for (int i = 0; i < DataList.Length; i++)
			{
				DataList[i] = new SideData(i);
				DataList[i].SetManager(this);
			}
		}

#if UNITY_EDITOR
		private IEnumerator Start()
#else
		private void Start()
#endif
		{
#if UNITY_EDITOR
			if (UserData.Instance.userModel == null)
			{
				yield return StartCoroutine(ResourceManager.Inst.PatchDataFile(null, () => { }));

				GameData.Init();
				EventData.Init();
				CashData.Init();

				Action<ResponseModel<UserModel>> eventLogin = response =>
				{
					UserData.Instance.userModel = response.data;
					UserData.Instance.userModel.id = -1;

					StartBattle();
				};

				if (string.IsNullOrEmpty(cheat.loginAccount))
				{
					var response = JsonFx.Json.JsonReader.Deserialize<ResponseModel<UserModel>>("{\"responseResult\":{\"completeIdList\":[]},\"data\":{\"id\":30,\"serverId\":1,\"account\":\"f300\",\"name\":\"300\",\"platformType\":0,\"paltformToken\":null,\"state\":0,\"sectorUnion\":7,\"level\":1,\"exp\":0,\"cash\":1000,\"kina\":0,\"orderPoint\":50,\"maxOrderPoint\":50,\"winPoint\":1000,\"abyssPoint\":0,\"regions\":{\"expansionWarId\":0,\"expansionWarStep\":0,\"regionIds\":[100],\"regionList\":[{\"id\":100,\"regionLevel\":1,\"regionExp\":0,\"spotOpen\":0,\"dispatchMList\":[],\"endDispatch\":null,\"raidBossLevelMap\":{},\"remainDispatchTime\":0,\"levelUp\":false}]},\"friendCount\":0,\"friendReqCount\":0,\"inventory\":[{\"id\":10006,\"itemType\":0,\"level\":0,\"amount\":1,\"strengFailCount\":0},{\"id\":100010,\"itemType\":0,\"level\":0,\"amount\":1,\"strengFailCount\":0},{\"id\":40001,\"itemType\":0,\"level\":0,\"amount\":1,\"strengFailCount\":0},{\"id\":30004,\"itemType\":0,\"level\":0,\"amount\":1,\"strengFailCount\":0},{\"id\":80008,\"itemType\":0,\"level\":0,\"amount\":1,\"strengFailCount\":0}],\"powerShards\":0,\"members\":[{\"no\":1,\"name\":102,\"level\":1,\"exp\":0,\"model\":7,\"state\":0,\"rarity\":5,\"strengthening\":0,\"traitPoint\":1,\"traits\":[1,16,110,101,107],\"traitLevels\":[0,0,0,0,0],\"skin\":0,\"rightItem\":null,\"leftItem\":null,\"strengCount\":0,\"strengFailCount\":0,\"origionCode\":0,\"maxTraitPoint\":1},{\"no\":2,\"name\":101,\"level\":1,\"exp\":0,\"model\":68,\"state\":0,\"rarity\":2,\"strengthening\":0,\"traitPoint\":1,\"traits\":[1,18,0,0,0],\"traitLevels\":[0,0,0,0,0],\"skin\":0,\"rightItem\":null,\"leftItem\":null,\"strengCount\":0,\"strengFailCount\":0,\"origionCode\":0,\"maxTraitPoint\":1},{\"no\":3,\"name\":107,\"level\":1,\"exp\":0,\"model\":66,\"state\":0,\"rarity\":2,\"strengthening\":0,\"traitPoint\":1,\"traits\":[1,15,0,0,0],\"traitLevels\":[0,0,0,0,0],\"skin\":0,\"rightItem\":null,\"leftItem\":null,\"strengCount\":0,\"strengFailCount\":0,\"origionCode\":0,\"maxTraitPoint\":1},{\"no\":4,\"name\":103,\"level\":1,\"exp\":0,\"model\":4,\"state\":0,\"rarity\":3,\"strengthening\":0,\"traitPoint\":1,\"traits\":[1,16,102,0,0],\"traitLevels\":[0,0,0,0,0],\"skin\":0,\"rightItem\":null,\"leftItem\":null,\"strengCount\":0,\"strengFailCount\":0,\"origionCode\":0,\"maxTraitPoint\":1},{\"no\":5,\"name\":108,\"level\":1,\"exp\":0,\"model\":37,\"state\":0,\"rarity\":2,\"strengthening\":0,\"traitPoint\":1,\"traits\":[1,22,0,0,0],\"traitLevels\":[0,0,0,0,0],\"skin\":0,\"rightItem\":null,\"leftItem\":null,\"strengCount\":0,\"strengFailCount\":0,\"origionCode\":0,\"maxTraitPoint\":1},{\"no\":6,\"name\":105,\"level\":1,\"exp\":0,\"model\":16,\"state\":0,\"rarity\":5,\"strengthening\":0,\"traitPoint\":1,\"traits\":[1,18,102,103,104],\"traitLevels\":[0,0,0,0,0],\"skin\":0,\"rightItem\":null,\"leftItem\":null,\"strengCount\":0,\"strengFailCount\":0,\"origionCode\":0,\"maxTraitPoint\":1},{\"no\":7,\"name\":109,\"level\":1,\"exp\":0,\"model\":67,\"state\":0,\"rarity\":2,\"strengthening\":0,\"traitPoint\":1,\"traits\":[1,15,0,0,0],\"traitLevels\":[0,0,0,0,0],\"skin\":0,\"rightItem\":null,\"leftItem\":null,\"strengCount\":0,\"strengFailCount\":0,\"origionCode\":0,\"maxTraitPoint\":1},{\"no\":8,\"name\":109,\"level\":1,\"exp\":0,\"model\":1,\"state\":0,\"rarity\":3,\"strengthening\":0,\"traitPoint\":1,\"traits\":[1,16,106,0,0],\"traitLevels\":[0,0,0,0,0],\"skin\":0,\"rightItem\":null,\"leftItem\":null,\"strengCount\":0,\"strengFailCount\":0,\"origionCode\":0,\"maxTraitPoint\":1},{\"no\":9,\"name\":7,\"level\":1,\"exp\":0,\"model\":56,\"state\":0,\"rarity\":3,\"strengthening\":0,\"traitPoint\":1,\"traits\":[1,21,102,0,0],\"traitLevels\":[0,0,0,0,0],\"skin\":0,\"rightItem\":null,\"leftItem\":null,\"strengCount\":0,\"strengFailCount\":0,\"origionCode\":0,\"maxTraitPoint\":1},{\"no\":10,\"name\":1,\"level\":1,\"exp\":0,\"model\":19,\"state\":0,\"rarity\":2,\"strengthening\":0,\"traitPoint\":1,\"traits\":[1,20,0,0,0],\"traitLevels\":[0,0,0,0,0],\"skin\":0,\"rightItem\":null,\"leftItem\":null,\"strengCount\":0,\"strengFailCount\":0,\"origionCode\":0,\"maxTraitPoint\":1},{\"no\":11,\"name\":3,\"level\":1,\"exp\":0,\"model\":63,\"state\":0,\"rarity\":2,\"strengthening\":0,\"traitPoint\":1,\"traits\":[1,17,0,0,0],\"traitLevels\":[0,0,0,0,0],\"skin\":0,\"rightItem\":null,\"leftItem\":null,\"strengCount\":0,\"strengFailCount\":0,\"origionCode\":0,\"maxTraitPoint\":1},{\"no\":12,\"name\":4,\"level\":1,\"exp\":0,\"model\":28,\"state\":0,\"rarity\":4,\"strengthening\":0,\"traitPoint\":1,\"traits\":[1,19,106,100,0],\"traitLevels\":[0,0,0,0,0],\"skin\":0,\"rightItem\":null,\"leftItem\":null,\"strengCount\":0,\"strengFailCount\":0,\"origionCode\":0,\"maxTraitPoint\":1},{\"no\":13,\"name\":5,\"level\":1,\"exp\":0,\"model\":51,\"state\":0,\"rarity\":2,\"strengthening\":0,\"traitPoint\":1,\"traits\":[1,17,0,0,0],\"traitLevels\":[0,0,0,0,0],\"skin\":0,\"rightItem\":null,\"leftItem\":null,\"strengCount\":0,\"strengFailCount\":0,\"origionCode\":0,\"maxTraitPoint\":1},{\"no\":14,\"name\":2,\"level\":1,\"exp\":0,\"model\":60,\"state\":0,\"rarity\":3,\"strengthening\":0,\"traitPoint\":1,\"traits\":[1,21,111,0,0],\"traitLevels\":[0,0,0,0,0],\"skin\":0,\"rightItem\":null,\"leftItem\":null,\"strengCount\":0,\"strengFailCount\":0,\"origionCode\":0,\"maxTraitPoint\":1},{\"no\":15,\"name\":4,\"level\":1,\"exp\":0,\"model\":64,\"state\":0,\"rarity\":2,\"strengthening\":0,\"traitPoint\":1,\"traits\":[1,17,0,0,0],\"traitLevels\":[0,0,0,0,0],\"skin\":0,\"rightItem\":null,\"leftItem\":null,\"strengCount\":0,\"strengFailCount\":0,\"origionCode\":0,\"maxTraitPoint\":1},{\"no\":16,\"name\":8,\"level\":1,\"exp\":0,\"model\":57,\"state\":0,\"rarity\":3,\"strengthening\":0,\"traitPoint\":1,\"traits\":[1,21,113,0,0],\"traitLevels\":[0,0,0,0,0],\"skin\":0,\"rightItem\":null,\"leftItem\":null,\"strengCount\":0,\"strengFailCount\":0,\"origionCode\":0,\"maxTraitPoint\":1},{\"no\":17,\"name\":3,\"level\":1,\"exp\":0,\"model\":25,\"state\":0,\"rarity\":5,\"strengthening\":0,\"traitPoint\":1,\"traits\":[1,20,112,105,110],\"traitLevels\":[0,0,0,0,0],\"skin\":0,\"rightItem\":null,\"leftItem\":null,\"strengCount\":0,\"strengFailCount\":0,\"origionCode\":0,\"maxTraitPoint\":1},{\"no\":18,\"name\":2,\"level\":1,\"exp\":0,\"model\":22,\"state\":0,\"rarity\":4,\"strengthening\":0,\"traitPoint\":1,\"traits\":[1,20,113,108,0],\"traitLevels\":[0,0,0,0,0],\"skin\":0,\"rightItem\":null,\"leftItem\":null,\"strengCount\":0,\"strengFailCount\":0,\"origionCode\":0,\"maxTraitPoint\":1},{\"no\":19,\"name\":1,\"level\":1,\"exp\":0,\"model\":59,\"state\":0,\"rarity\":2,\"strengthening\":0,\"traitPoint\":1,\"traits\":[1,21,0,0,0],\"traitLevels\":[0,0,0,0,0],\"skin\":0,\"rightItem\":null,\"leftItem\":null,\"strengCount\":0,\"strengFailCount\":0,\"origionCode\":0,\"maxTraitPoint\":1},{\"no\":20,\"name\":6,\"level\":1,\"exp\":0,\"model\":55,\"state\":0,\"rarity\":2,\"strengthening\":0,\"traitPoint\":1,\"traits\":[1,21,0,0,0],\"traitLevels\":[0,0,0,0,0],\"skin\":0,\"rightItem\":null,\"leftItem\":null,\"strengCount\":0,\"strengFailCount\":0,\"origionCode\":0,\"maxTraitPoint\":1}],\"waitMembers\":[],\"choiceMembers\":[1,2,3,4],\"memberBattleArray\":[],\"dispatchResult\":null,\"quickPvpInfo\":null,\"questInfo\":{\"questId\":1,\"state\":2,\"value\":0},\"newAlarmCount\":0,\"lastAccessed\":1417155453570,\"lastLogin\":1417155453570,\"regDate\":1416969824927,\"lastBuyItemId\":0,\"raidBoss\":false,\"achiCompleteCount\":0,\"isPowerShard\":false,\"isProtected\":false},\"ecode\":0}");

					eventLogin(response);
				}
				else
					NetworkManager.Instance.C2S_LOGIN(AuthPlatformID.None, cheat.loginAccount, string.Empty, eventLogin, null);
			}
			else
#endif
				StartBattle();
		}

		private void OnDestroy()
		{
			Application.targetFrameRate = -1;
		}

		protected MemberModel FindMemberData(string _memmodelNo)
		{
			int memNo = int.Parse(_memmodelNo);
			foreach (var pair in UserData.Instance.userModel.members_dic)
			{
				if (pair.Value.no == memNo)
					return pair.Value;
			}
			return null;
		}

		/// <summary>
		/// 초기화.
		/// </summary>
		protected void Init(int seed)
		{
			foreach (SideData data in DataList)
				data.Clear();

			IsActive = false;	// 전투 활성 여부 초기화.
			UI.IsActive = true;
			UI.IsX4ButtonActive = true;
			Report.Clear();

			typeFlag = 0;	// 공격 가능 타입 초기화.

			LeftTime = 180f;
			LeftTimeCur = LeftTime;	// 남은 시간 초기화.

			TacticsType = ETactics.NONE;
			TacticsPointPerCount = 250f;
			TacticsCount = 5;
			TacticsCountCur = 1;	// 전술 포인트 초기화.

			if (Monitor != null)
				Monitor.Stop();
			Monitor = ProtectMonitor.Create(seed);	// 암호화 초기화.
			StartCoroutine(Monitor.Start());

			InitTake();	// 카메라 테이크 초기화.
			InitPlayer();	// 플레이어 초기화.
			InitEnemy();	// 적 초기화.

			Application.targetFrameRate = 24;
		}

		/// <summary>
		/// 카메라 테이크 초기화.
		/// </summary>
		private void InitTake()
		{
			StopTake();

			EventlineStaticActor.Camera = cameraList[0];
			EventlineStaticActor.MiddleCam = cameraList[1];
			EventlineStaticActor.MainCamera = cameraList[2];

			takeDic.Clear();
			foreach (var id in takePlayOrder.SelectMany(order => order.Select(id => id)))
				takeDic[id] = TakePool[string.Format("camera/BossIdle_{0}", id)];

			takeIndexCur = 0;
		}

		/// <summary>
		/// 아군 유닛 초기화.
		/// </summary>
		private void InitPlayer()
		{
			bool bHasFormation = UserData.Instance.userModel.HasFormation();
			if (!bHasFormation && ArmyDeploy.deployedPawnList.Count == 0)
			{
				UIDeployPanel.Instance.SetDeployPanel();
				UIDeployPanel.Instance.AutoDeployNew();
				ArmyDeploy.Instance.OnStart();
			}

			var sideData = DataList[ESide.PLAYER];

			IList<GameObject> memberList = bHasFormation ? UserData.Instance.userModel.m_3DmodelWithFormation as IList<GameObject> : ArmyDeploy.deployedPawnList;

			for (int i = 0, j = 0; i < 3; i++)
			{
				for (int k = 0; k < 8 - i; k++, j++)
				{
					GameObject member = memberList[j];

					if (member == null)
						continue;

					member.SetActive(true);

					var memberData = FindMemberData(member.name);
					var area = EArea.NONE;

					InitPlayerPosition(member, i, k, ref area);

					var controller = new GameObject("Controller", typeof(Controller.MemberController)).GetComponent<Controller.MemberController>();

					sideData.CharacterList.Add(controller);

					controller.SetManager(this);
					controller.ModelGameObject = member;
					controller.Init(memberData, ESide.PLAYER, (ELine)i, k, area);
				}
			}

			sideData.InitHealthPoint();
			sideData.UpdateHealthPoint();

			UI.NamePlayer = UserData.Instance.userModel.name;
			UI.SetPortraitPlayer(sideData.CharacterList[0]);
			UI.CountPlayerTotal = sideData.CharacterList.Count;
			UI.CountPlayer = sideData.CharacterList.Count;
		}

		protected abstract void InitPlayerPosition(GameObject member, int line, int pos, ref EArea area);

		protected abstract void InitEnemy();
		#endregion

		#region Position
		/// <summary>
		/// 주어진 공간을 메꿀 수 있는 유닛을 획득.
		/// </summary>
		private Controller.BaseController GetEmptyPositionCandidate(ESide side, ELine line, int pos)
		{
			int lineNext = (int)line;
			IEnumerable<Controller.BaseController> candidateList;

			// 유닛이 존재할 때까지 뒷열로 넘어가면서 탐색.
			do
			{
				lineNext++;
				if (lineNext >= (int)ELine.END)
					return null;

				candidateList = DataList[side].CharacterList.Where(data => data.IsAlive && data.Line == (ELine)lineNext).Except(DataList[side].WaitList.Keys);
			} while (!candidateList.Any());

			// 유닛이 존재할 경우 액션 타임이 가장 적게 남은 가진 유닛을 선택.
			return candidateList.OrderBy(data => data.ActionTimeLeft).FirstOrDefault();
		}

		/// <summary>
		/// 유닛이 사망하였을 때. 없애버림.
		/// </summary>
		public void RemoveMember(Controller.BaseController source)
		{
			if (source.Line != ELine.FIRST)
				return;

			var target = GetEmptyPositionCandidate(source.Side, source.Line, source.Pos);	// 빈 부분을 채울 대상을 획득.

			if (target == null)
				return;

			if (target.IsTakeIdle)
				FillEmptyPosition(source, target);
			else
				ReserveToFillEmptyPosition(source, target);
		}

		private void FillEmptyPosition(Controller.BaseController source, Controller.BaseController target)
		{
			target.SetPosition(source.Line, source.Pos, source.Area);
			target.MoveToEmptyPosition(source.Position);
		}

		/// <summary>
		/// 즉시 빈 공간을 메꿀 수 없는 경우. 예약.
		/// </summary>
		private void ReserveToFillEmptyPosition(Controller.BaseController source, Controller.BaseController target)
		{
			DataList[target.Side].WaitList.Add(target, source);

			target.EventTakeEnd += FillReservedEmptyPosition;
		}

		/// <summary>
		/// 예약된 빈 공간을 메꿈.
		/// </summary>
		private void FillReservedEmptyPosition(Controller.BaseController source)
		{
			source.EventTakeEnd -= FillReservedEmptyPosition;

			FillEmptyPosition(DataList[source.Side].WaitList[source], source);

			DataList[source.Side].WaitList.Remove(source);
		}
		#endregion

		#region Logic
		private void Update()
		{
			if (!IsActive)
				return;

			LeftTimeCur -= Time.deltaTime;

			CheckBattleEndWithLeftTime();
		}

		public IEnumerable<Controller.BaseController> GetList(Controller.BaseController source)
		{
			var side = source.IsSidePlayer ? ESide.ENEMY : ESide.PLAYER;

			 return DataList[side].CharacterList.Where(data => data.IsAlive);
		}

		/// <summary>
		/// 회복할 목표를 획득.
		/// </summary>
		public Controller.BaseController GetHealTarget(ESide side)
		{
			var candidateList = DataList[side].CharacterList.Where(data => data.IsAlive).OrderBy(data => (float)data.HealthPointCur / data.Stat.baseHP.Get(Monitor)).ToList();
			int index;

			if (candidateList.Count == 0)
				return null;

			switch (candidateList.Count)
			{
				case 1: index = 0; break;
				case 2: index = Utility.SelectRandomIndex(70, 30); break;
				default: index = Utility.SelectRandomIndex(60, 30, 10); break;
			}

			if (index < 0 || index >= candidateList.Count)
				return null;

			return candidateList[index];
		}

		/// <summary>
		/// 스킬 대상 획득.
		/// </summary>
		public IList<Controller.BaseController> GetSkillTarget(Controller.BaseController source)
		{
			if (source.SkillData == null)
				return null;

			var side = source.IsSidePlayer ? ESide.ENEMY : ESide.PLAYER;
			IList<Controller.BaseController> candidateList = null;

			if (source.SkillData.targetType == 1)
				candidateList = DataList[side].CharacterList.Where(data => data.IsAlive && data.Line == ELine.FIRST).ToList();
			else if (source.SkillData.targetType == 3)
				candidateList = DataList[side].CharacterList.Where(data => data.IsAlive).ToList();

			if (candidateList == null)
				return null;

			var tauntList = candidateList.Select(data => Mathf.Pow(data.Stat.taunt.Get(Monitor), 1.15f)).ToList();
			var resultList = new List<Controller.BaseController>();

			for (int i = 0; i < source.SkillData.targetNumber; i++)
			{
				if (candidateList.Count <= 0)
					break;

				int index = candidateList.Count == 1 ? 0 : Utility.SelectRandomIndex(tauntList);

				if (index == -1)
					break;

				resultList.Add(candidateList[index]);

				candidateList.RemoveAt(index);
				tauntList.RemoveAt(index);
			}

			return resultList.Count <= 0 ? null : resultList;
		}

		/// <summary>
		/// 주어진 공격 타입이 유효한지 반환.
		/// </summary>
		public bool IsAttackTypeValid(int type)
		{
			if (typeFlag == 0)
				return true;

			return (typeFlag & (1 << type)) > 0;
		}

		/// <summary>
		/// 전투 종료 요청 응답 받았을 때.
		/// </summary>
		private void OnBattleEndResponse(ResponseModel<RaidBossEndModel> response)
		{
			for (int i = 0; i < UserData.Instance.userModel.m_3DmodelWithFormation.Length; i++)
			{
				if (UserData.Instance.userModel.m_3DmodelWithFormation[i] != null)
				{
					Destroy(UserData.Instance.userModel.m_3DmodelWithFormation[i]);

					UserData.Instance.userModel.m_3DmodelWithFormation[i] = null;
				}
			}

			UserData.Instance.raidBossResultData = response.data;

			UI.ShowBattleResult(Report);
		}

		protected void OnBattleEndResponse(ResponseModel<UserModel> response)
		{
			for (int i = 0; i < UserData.Instance.userModel.m_3DmodelWithFormation.Length; i++)
			{
				if (UserData.Instance.userModel.m_3DmodelWithFormation[i] != null)
				{
					Destroy(UserData.Instance.userModel.m_3DmodelWithFormation[i]);

					UserData.Instance.userModel.m_3DmodelWithFormation[i] = null;
				}
			}

			UserData.Instance.userModel = response.data;

			UI.ShowBattleResult(Report);

			//레이드보스 관련 퀘스트 완료 여부 체크를 위해 퀘스트 정보 갱신.
			Invoke("GetQuestInfo", 1f);
		}

		void GetQuestInfo()
		{
			NetworkManager.Instance.C2S_GET_QUESTINFO(QuestManager.instance.ResponseGetQuestInfo);
		}

		/// <summary>
		/// 총 HP 비교로 전투가 끝났는지 체크.
		/// </summary>
		private void CheckBattleEndWithHealthPoint()
		{
			bool isPlayerDie = DataList[ESide.PLAYER].HealthPointCur <= 0f;
			bool isEnemyDie = DataList[ESide.ENEMY].HealthPointCur <= 0f;

			if (isPlayerDie || isEnemyDie)
			{
				PauseBattle();

				if (isPlayerDie)
					EndBattle(false);	// 승리 여부.
			}
		}

		/// <summary>
		/// 남은 시간 비교로 전투가 끝났는지 체크.
		/// </summary>
		private void CheckBattleEndWithLeftTime()
		{
			if (LeftTimeCur <= 0)
			{
				PauseBattle();

				EndBattle(false);
			}
		}

		/// <summary>
		/// 공격 가능한 유닛이 남아있는지 체크.
		/// </summary>
		private void CheckBattleEndWithAttackType()
		{
			// 공격 가능한 유닛이 존재하지 않으면 패배 확정.
			if (!DataList[ESide.PLAYER].CharacterList.Any(data => data.IsAlive && IsAttackTypeValid(data.Stat.atkType.Get(Monitor))))
			{
				PauseBattle();

				UI.ShowNofiticationEffect(TextController.GetStringWithId("raidNoticeDisableAttack"), () => EndBattle(false));
			}
		}

		/// <summary>
		/// 전투 시작.
		/// </summary>
		public abstract void StartBattle();

		/// <summary>
		/// 전투 종료.
		/// </summary>
		protected void EndBattle(bool isWin)
		{
			Application.targetFrameRate = -1;

			UI.OnX4ButtonRelease();
			UI.IsX4ButtonActive = false;

			Report.SetBattleResult(isWin);
			Report.SetBattleTime(LeftTime - LeftTimeCur);

			if (UserData.Instance.userModel.id > 0)
				RequestBattleEnd(isWin);
			else
				UI.ShowBattleResult(Report);
		}

		public void PauseBattle()
		{
			IsActive = false;
		}

		public void ResumeBattle()
		{
#if UNITY_EDITOR
			if (cheat.isTester)
				return;
#endif

			IsActive = true;
		}

		protected abstract void RequestBattleEnd(bool isWin);
		#endregion

		#region Health Point / Flag
		/// <summary>
		/// 총 HP 갱신.
		/// </summary>
		public void UpdateHealthPoint(ESide side)
		{
			DataList[side].UpdateHealthPoint();

			CheckBattleEndWithHealthPoint();
			CheckBattleEndWithAttackType();
		}

		/// <summary>
		/// 총 HP 갱신.
		/// </summary>
		public void DecreaseHealthPoint(ESide side, int point)
		{
			DataList[side].UpdateHealthPoint(point);

			CheckBattleEndWithHealthPoint();
		}
		#endregion

		#region Tactics Point / Tactics
		/// <summary>
		/// TP 갱신.
		/// </summary>
		public void IncreaseTacticsPoint(float point)
		{
			TacticsPointCur += point;
		}

		/// <summary>
		/// TP 갱신.
		/// </summary>
		public void IncreaseTacticsCount(int point)
		{
			TacticsPointCur += point * TacticsPointPerCount;
		}

		/// <summary>
		/// 치료 전술. Heeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeal.
		/// </summary>
		public void ExecuteHealTactics()
		{
			IncreaseTacticsCount(-1);

			var healerList = DataList[ESide.PLAYER].CharacterList.Where(data => data.IsAlive && data.IsHealer).Select(data => data.Stat);
			var candidateList = DataList[ESide.PLAYER].CharacterList.Where(data => data.IsAlive && data.Line == ELine.FIRST);

			int point = Utility.GetHealPointWhenHealTactics(this, healerList, candidateList.Count());

			foreach (var candidate in candidateList)
				candidate.Heal(point);

			var effect = effectTacticsList[(int)ETactics.HEAL - 1];
			effect = Instantiate(effect) as GameObject;
			effect.transform.position = effectTacticsPosition.transform.position;
			effect.transform.rotation = Quaternion.identity;
			effect.transform.localScale = Vector3.one;
		}

		/// <summary>
		/// 방어 전술.
		/// </summary>
		public void ExecuteDefenceTactics()
		{
			IncreaseTacticsCount(-1);

			var effect = effectTacticsList[(int)ETactics.DEFENCE - 1];
			effect = Instantiate(effect) as GameObject;
			effect.transform.position = effectTacticsPosition.transform.position;
			effect.transform.rotation = Quaternion.identity;
			effect.transform.localScale = Vector3.one;

			StartCoroutine(ExecuteDefenceTacticsCoroutine());
		}

		private IEnumerator ExecuteDefenceTacticsCoroutine()
		{
			TacticsType = ETactics.DEFENCE;

			yield return new WaitForSeconds(5f);

			TacticsType = ETactics.NONE;
		}

		/// <summary>
		/// 총공격 전술.
		/// </summary>
		public void ExecuteAttackTactics()
		{
			IncreaseTacticsCount(-1);

			var effect = effectTacticsList[(int)ETactics.ATTACK - 1];
			effect = Instantiate(effect) as GameObject;
			effect.transform.position = effectTacticsPosition.transform.position;
			effect.transform.rotation = Quaternion.identity;
			effect.transform.localScale = Vector3.one;

			StartCoroutine(ExecuteAttackTacticsCoroutine());
		}

		private IEnumerator ExecuteAttackTacticsCoroutine()
		{
			TacticsType = ETactics.ATTACK;

			yield return new WaitForSeconds(5f);

			TacticsType = ETactics.NONE;
		}
		#endregion

		#region Take
		/// <summary>
		/// 다음 테이크 재생.
		/// </summary>
		public void PlayNextTake(bool isItp = true)
		{
			StopTake();

			if (takeIndexCur < 0 || takeIndexCur >= takePlayOrder[Phase - 1].Length)
				takeIndexCur = 0;

			takeCur = Eventline.Play(takeDic[takePlayOrder[Phase - 1][takeIndexCur]], new[] { cameraList[2] }, gameObject, false, enter_time: isItp ? cameraItpTime : 0f);

			takeIndexCur++;
		}

		/// <summary>
		/// 테이크 정지.
		/// </summary>
		public void StopTake()
		{
			if (takeCur != null)
			{
				takeCur.Stop();
				takeCur = null;
			}
		}

		/// <summary>
		/// 테이크 재생 끝났을 때.
		/// </summary>
		protected void EndOfPlay(IEventlinePlayer param)
		{
			PlayNextTake();
		}
		#endregion
	}

	public abstract partial class BaseManager : MonoBehaviour
	{
		public class SideData : IWorker
		{
			public BaseManager Manager { get; private set; }

			private readonly ESide sideType;

			private int healthPointCur;

			public List<Controller.BaseController> CharacterList { get; private set; }

			public int HealthPoint { get; private set; }
			public int HealthPointCur
			{
				get
				{
					return healthPointCur;
				}

				private set
				{
					healthPointCur = Mathf.Clamp(value, 0, HealthPoint);

					if (HealthPoint <= 0)
						return;

					float ratio = (float)HealthPointCur / HealthPoint;

					if (sideType == ESide.PLAYER)
						Manager.UI.HpPlayer = ratio;
					else if (sideType == ESide.ENEMY)
						Manager.UI.HpEnemy = ratio;
				}
			}

			public Dictionary<Controller.BaseController, Controller.BaseController> WaitList { get; private set; }

			public SideData(ESide side)
			{
				CharacterList = new List<Controller.BaseController>();
				WaitList = new Dictionary<Controller.BaseController, Controller.BaseController>();

				sideType = side;

				Clear();
			}

			public void Clear()
			{
				foreach (var character in CharacterList)
					Destroy(character);
				CharacterList.Clear();

				WaitList.Clear();

				HealthPoint = 0;
				HealthPointCur = 0;
			}

			/// <summary>
			/// 총 HP 초기화.
			/// </summary>
			public void InitHealthPoint()
			{
				HealthPoint = 0;
				foreach (var character in CharacterList.Where(data => data.IsAlive))
					HealthPoint += character.Stat.baseHP.Get(Manager.Monitor);
			}

			/// <summary>
			/// 총 HP 갱신.
			/// </summary>
			public void UpdateHealthPoint()
			{
				HealthPointCur = 0;
				foreach (var character in CharacterList.Where(data => data.IsAlive))
					HealthPointCur += character.HealthPointCur;
			}

			/// <summary>
			/// 총 HP 갱신.
			/// </summary>
			public void UpdateHealthPoint(int point)
			{
				HealthPointCur += point;
			}

			public void SetManager(BaseManager manager)
			{
				Manager = manager;
			}
		}
	}
}
